﻿/* **************************************************************************
 *
 * Copyright 2007-2010 Jeff Hardy
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * *************************************************************************/

using System;
using System.IO;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

namespace NWSGI
{
    class WsgiApplication
    {
        private ScriptEngine engine;
        private IWsgiConfig config;

        private WsgiCallableDelegate application;
        private DateTime? lastModified;
        private string fullPath;
        private string callable;

        public WsgiApplication(ScriptEngine engine, IWsgiConfig config, IWsgiContext context, string path, string callable)
        {
            this.engine = engine;
            this.config = config;

            GetApplicationInfo(context, path, callable);
        }

        public void Run(IWsgiContext context)
        {
            UpdateApplicationInfo(context);

            WsgiExecutor exec = new WsgiExecutor(engine, config, context);
            exec.Run(application);
        }

        /// <summary>
        /// Fill out the ApplicationInfo values for an application.
        /// </summary>
        /// <param name="path">The path to an application, or null if callable is an entry point.</param>
        /// <param name="callable">The name of the callable if phyiscalPatrh is set, or an entry point.</param>
        /// <returns>The ApplicationInfo struct describing the application.</returns>
        private void GetApplicationInfo(IWsgiContext context, string path, string callable)
        {
            if(string.IsNullOrEmpty(path))
            {
                if(string.IsNullOrEmpty(callable))
                    throw new ArgumentNullException("callable", "Either 'physicalPath' or 'callable' must be set.");

                // The module does not "exist", so split the callable into module.callable,
                // import the module, and call the callable. If the callable ends with '()'
                // we must call it and then call the result (used for classes).

                int n = callable.LastIndexOf('.');
                if(n < 0)
                    throw new ArgumentException("callable is not an entry point.", "callable");

                bool mustCallCallable = callable.EndsWith("()");

                string module = callable.Substring(0, n);
                string callableName = (mustCallCallable ? callable.Substring(0, callable.Length - 2) : callable).Substring(n + 1);

                ScriptScope scope = engine.ImportModule(module);

                if(scope.ContainsVariable(callableName))
                {
                    object callableObj = scope.GetVariable(callableName);
                    if(!engine.Operations.IsCallable(callableObj))
                        throw new InvalidOperationException(string.Format("{0} is not callable in the module {1}.", callableName, module));

                    if(mustCallCallable)
                        callableObj = engine.Operations.Invoke(callableObj);

                    this.application = engine.Operations.ConvertTo<WsgiCallableDelegate>(callableObj);
                    this.callable = callable;
                }
                else
                {
                    throw new InvalidOperationException(string.Format("{0} was not found in {1}", callableName, module));
                }
            }
            else
            {
                // The module is a file on disk, so use that
                string fullPath = context.MapPath(path);

                if(!File.Exists(fullPath))
                    throw new InvalidOperationException(string.Format("Module path '{0}' does not exist or is not accessible.", path));

                // compile the application module and execute it
                ScriptScope scope = engine.CreateScope();
                CompiledCode code = engine.CreateScriptSourceFromFile(fullPath).Compile();
                code.Execute(scope);

                this.application = scope.GetVariable<WsgiCallableDelegate>(callable);
                this.fullPath = fullPath;
                this.lastModified = File.GetLastWriteTimeUtc(fullPath);
                this.callable = callable;
            }
        }

        /// <summary>
        /// Update an application callable if the file has changed. This only affects physical modules
        /// and not entry points.
        /// </summary>
        /// <param name="application">The application info to update.</param>
        private void UpdateApplicationInfo(IWsgiContext context)
        {
            if(!string.IsNullOrEmpty(fullPath) && lastModified != null)
            {
                if(File.GetLastWriteTimeUtc(fullPath) > lastModified)
                {
                    GetApplicationInfo(context, fullPath, callable);
                    lastModified = File.GetLastWriteTimeUtc(fullPath);
                }
            }
        }
    }
}
